﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="With_MethodInfoExtensions.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the With_MethodInfoExtensions type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Relection
{
    using System;
    using System.Linq;
    using System.Reflection;
    using Assets;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Reflection;
    using Testing;

#if SILVERLIGHT

    using Microsoft.Silverlight.Testing;

#endif

    [TestClass, Tag("StaticInvokeFactory")]
    public class With_MethodInfoExtensions
    {
        protected Subscriber Subscriber { get; set; }

        [TestInitialize]
        public void TestInitialize()
        {
            Subscriber = new Subscriber();
            Subscriber.SetReturn(1);
        }

        public static MethodInfo GetOnInvokePrivateFuncMethod(params Type[] genericTypeArguments)
        {
            var method = typeof(Subscriber).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).FirstOrDefault(x => x.Name ==
                "OnInvokePrivateFunc" && x.GetGenericArguments().Length == genericTypeArguments.Length);
            Assert.IsNotNull(method);
            return method.IsGenericMethodDefinition ? method.MakeGenericMethod(genericTypeArguments) : method;
        }

        public static MethodInfo GetOnInvokePrivateActionMethod(params Type[] genericTypeArguments)
        {
            var method = typeof(Subscriber).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).FirstOrDefault(x => x.Name ==
                "OnInvokePrivateAction" && x.GetGenericArguments().Length == genericTypeArguments.Length);
            Assert.IsNotNull(method);
            return method.IsGenericMethodDefinition ? method.MakeGenericMethod(genericTypeArguments) : method;
        }

        [TestMethod]
        public void Should_support_non_public_action_with_no_arguments()
        {
            var action = GetOnInvokePrivateActionMethod().CreateAction<Subscriber>();
            action(Subscriber);
            Subscriber.AssertSingleLogEntry();
        }

        [TestMethod]
        public void Should_support_non_public_action_with_one_argument()
        {
            var action = GetOnInvokePrivateActionMethod(typeof(int)).CreateAction<Subscriber, int>();
            action(Subscriber, 1);
            Subscriber.AssertSingleLogEntry(1);
        }

        [TestMethod]
        public void Should_support_non_public_action_with_two_arguments()
        {
            var action = GetOnInvokePrivateActionMethod(typeof(int), typeof(string)).CreateAction<Subscriber, int, string>();
            action(Subscriber, 1, "Hello World");
            Subscriber.AssertSingleLogEntry(1, "Hello World");
        }

        [TestMethod]
        public void Should_support_non_public_action_with_three_arguments()
        {
            var action = GetOnInvokePrivateActionMethod(typeof(int), typeof(string), typeof(double)).CreateAction<Subscriber, int, string, double>();
            action(Subscriber, 1, "Hello World", 2.1);
            Subscriber.AssertSingleLogEntry(1, "Hello World", 2.1);
        }

        [TestMethod]
        public void Should_support_non_public_action_with_four_arguments()
        {
            var action = GetOnInvokePrivateActionMethod(typeof(int), typeof(string), typeof(double), typeof(object)).CreateAction<Subscriber, int, string, double, object>();
            action(Subscriber, 1, "Hello World", 2.1, Subscriber);
            Subscriber.AssertSingleLogEntry(1, "Hello World", 2.1, Subscriber);
        }

        [TestMethod]
        public void Should_support_non_public_func_with_no_arguments()
        {
            var func = GetOnInvokePrivateFuncMethod(typeof(int)).CreateFunc<Subscriber, int>();
            Assert.AreEqual(1, func(Subscriber));
            Subscriber.AssertSingleLogEntry();
        }

        [TestMethod]
        public void Should_support_non_public_func_with_one_argument()
        {
            var func = GetOnInvokePrivateFuncMethod(typeof(int), typeof(int)).CreateFunc<Subscriber, int, int>();
            Assert.AreEqual(1, func(Subscriber, 1));
            Subscriber.AssertSingleLogEntry(1);
        }

        [TestMethod]
        public void Should_support_non_public_func_with_two_arguments()
        {
            var func = GetOnInvokePrivateFuncMethod(typeof(int), typeof(string), typeof(int)).CreateFunc<Subscriber, int, string, int>();
            Assert.AreEqual(1, func(Subscriber, 1, "Hello World"));
            Subscriber.AssertSingleLogEntry(1, "Hello World");
        }

        [TestMethod]
        public void Should_support_non_public_func_with_three_arguments()
        {
            var func = GetOnInvokePrivateFuncMethod(typeof(int), typeof(string), typeof(double), typeof(int)).CreateFunc<Subscriber, int, string, double, int>();
            Assert.AreEqual(1, func(Subscriber, 1, "Hello World", 2.1));
            Subscriber.AssertSingleLogEntry(1, "Hello World", 2.1);
        }

        [TestMethod]
        public void Should_support_non_public_func_with_four_arguments()
        {
            var func = GetOnInvokePrivateFuncMethod(typeof(int), typeof(string), typeof(double), typeof(object), typeof(int))
                .CreateFunc<Subscriber, int, string, double, object, int>();
            Assert.AreEqual(1, func(Subscriber, 1, "Hello World", 2.1, Subscriber));
            Subscriber.AssertSingleLogEntry(1, "Hello World", 2.1, Subscriber);
        }

        [TestMethod]
        public void Should_support_instance_casting()
        {
            GetOnInvokePrivateActionMethod().CreateAction<object>()(Subscriber);
            Subscriber.AssertSingleLogEntry();
        }

        [TestMethod]
        public void Should_throw_if_instance_is_incompatible_type()
        {
            AssertException.Throws<ArgumentException>(
                () => GetOnInvokePrivateActionMethod().CreateAction<Publisher>());
        }
        
        [TestMethod]
        public void Should_support_structs()
        {
            var instance = new DateTime(2000, 1, 1);
            var result = typeof(DateTime).GetMethod("AddDays")
                .CreateFunc<DateTime, double, DateTime>()(instance, 1);
            Assert.AreEqual(new DateTime(2000, 1, 2), result);
        }

        [TestMethod]
        public void Should_support_boxed_structs()
        {
            var instance = new DateTime(2000, 1, 1);
            var result = typeof(DateTime).GetMethod("AddDays")
                .CreateFunc<object, double, DateTime>()(instance, 1);
            Assert.AreEqual(new DateTime(2000, 1, 2), result);
        }

        [TestMethod]
        public void Should_throw_argument_exception_if_two_many_arguments_are_supplied()
        {
            AssertException.Throws<ArgumentException>(
                () => GetOnInvokePrivateActionMethod().CreateAction<Subscriber, int>());
        }

        [TestMethod]
        public void Should_throw_argument_exception_if_not_enough_arguments_are_supplied()
        {
            AssertException.Throws<ArgumentException>(
                () => GetOnInvokePrivateActionMethod(typeof(int)).CreateAction<Subscriber>());
        }

        [TestMethod]
        public void Should_throw_if_argument_types_are_incompatible()
        {
            AssertException.Throws<ArgumentException>(
                () => GetOnInvokePrivateActionMethod(typeof(int)).CreateAction<Subscriber, DateTime>());
        }

        [TestMethod]
        public void Should_coerce_to_nullable_of_same_type()
        {
            GetOnInvokePrivateActionMethod(typeof(int?))
                .CreateAction<Subscriber, int?>()(Subscriber, 1);
            Subscriber.AssertSingleLogEntry(1);
        }

        [TestMethod]
        public void Should_coerce_from_nullable_of_same_type()
        {
            GetOnInvokePrivateActionMethod(typeof(int))
                .CreateAction<Subscriber, int?>()(Subscriber, 1);
            Subscriber.AssertSingleLogEntry(1);
        }

        [TestMethod]
        public void Should_coerce_using_available_cast_operator()
        {
            GetOnInvokePrivateActionMethod(typeof(int))
                .CreateAction<Subscriber, double>()(Subscriber, 1.0);
            Subscriber.AssertSingleLogEntry(1);
        }

        [TestMethod]
        public void Should_coerce_from_object()
        {
            GetOnInvokePrivateActionMethod(typeof(int))
                .CreateAction<object, object>()(Subscriber, 1);
            Subscriber.AssertSingleLogEntry(1);
        }
    }
}
